// 2013年版权归Go作者所有。版权所有。
// 此源代码的使用受BSD样式的约束
// 可以在许可证文件中找到的许可证。

// 垃圾收集器活动位图生成。

// 命令行标志-live导致此代码打印调试信息。
// 这些级别是：
// None
// -实时（aka-live=1）：在安全点将活动列表打印为代码警告
// -live=2：打印带有活动性批注的程序集列表
// None
// 每个级别也包括早期输出。

package liveness

import (
	"crypto/md5"
	"crypto/sha1"
	"fmt"
	"os"
	"sort"
	"strings"

	"cmd/compile/internal/abi"
	"cmd/compile/internal/base"
	"cmd/compile/internal/bitvec"
	"cmd/compile/internal/ir"
	"cmd/compile/internal/objw"
	"cmd/compile/internal/reflectdata"
	"cmd/compile/internal/ssa"
	"cmd/compile/internal/typebits"
	"cmd/compile/internal/types"
	"cmd/internal/obj"
	"cmd/internal/objabi"
	"cmd/internal/src"
)

// OpVarDef是活动性分析的注释，用于标记位置
// 变量的完整初始化（定义）开始的地方。
// 因为活性分析可以看到单个单词的初始化
// 变量非常简单，OpVarDef仅用于多字
// 满足isfat（n.Type）的变量。不过为了简单起见，buildssa
// 发射OpVarDef，而不考虑可变宽度。
// None
// 指令流中的“OpVarDef x”注释说明了活动性
// 分析的行为就好像变量x在该位置初始化一样
// 指令流中的点。OpVarDef必须出现在
// 实际（多指令）初始化，并且必须在
// 以前值的任何使用（如果有）。例如，如果编译：
// None
// x=x[1:]
// None
// 生成以下代码非常重要：
// None
// 底座、透镜、盖=x[1:]
// OpVarDef x
// x={base，len，cap}
// None
// 相反，如果生成的代码如下所示：
// None
// OpVarDef x
// 底座、透镜、盖=x[1:]
// x={base，len，cap}
// None
// 然后活性分析将确定先前的x值为
// 不必要，即使x[1:]计算即将使用它。
// 类似地，如果生成的代码如下所示：
// None
// 底座、透镜、盖=x[1:]
// x={base，len，cap}
// OpVarDef x
// None
// 那么活性分析将不会保留新的x值，因为
// OpVarDef似乎“覆盖”了它。
// None
// OpVarDef是一个有点混乱的工作，围绕着指令
// stream正在处理单个单词的值，但活动性分析
// 想要处理单个变量，可能是多个单词
// 聚合。从某个角度来看，考虑出租可能是有道理的
// 活跃度分析也适用于单个词的值，尽管
// 在接口值、切片和字符串方面存在复杂性，
// 所有这些都不能作为单独的词来对待。
// None
// OpVarKill与OpVarDef相反：它将一个值标记为不再需要，
// 即使它的地址已经被记录了。也就是说，OpVarKill注释断言
// 它的论点肯定是死的，在进行活性分析时使用
// 否则就无法推断出这一事实。

// TODO:在这里除掉OpVarKill。它对于堆栈帧分配非常有用
// 因此编译器可以将两个temp分配到同一位置。现在在这里
// 无用，因为实现了堆栈对象。

// blockEffects总结了SSA块上的活动性效果。
type blockEffects struct {
	// 在Liveness.prolog期间仅使用
	// 个别街区：
	// None
	// uevar：向上暴露的变量（在块中设置之前使用）
	// varkill：终止变量（在块中设置）
	uevar   bitvec.BitVec
	varkill bitvec.BitVec

	// 使用控制流信息在Liveness.solve期间计算：
	// None
	// livein：变量在块条目处活动
	// liveout：变量在块出口处活动
	livein  bitvec.BitVec
	liveout bitvec.BitVec
}

// 活动性分析使用的全局状态的集合。
type liveness struct {
	fn         *ir.Func
	f          *ssa.Func
	vars       []*ir.Name
	idx        map[*ir.Name]int32
	stkptrsize int64

	be []blockEffects

	// allUnsafe表示此函数中的所有点都是
	// 不安全点。
	allUnsafe bool
	// 如果值ID i是不安全点，则设置未安全点位i
	// （不允许优先购买）。只有在以下情况下才有效！诱惑。
	unsafePoints bitvec.BitVec

	// 一个数组，该数组中的每个安全点都有一个位向量
	// liveness.epilogue期间的当前块。价值索引
	// 那个街区的订单。此外，对于入口块
	// livevars[0]是条目位图。紧凑的动作
	// 这些是堆叠地图。
	livevars []bitvec.BitVec

	// livenessMap将安全点（即调用）映射到它们的
	// 活性映射索引。
	livenessMap Map
	stackMapSet bvecSet
	stackMaps   []bitvec.BitVec

	cache progeffectscache

	// partLiveArgs包括可能
	// 部分地活着。也就是说，它被认为是活的，因为
	// 它的一部分已被使用，但我们可能无法初始化所有部分。
	partLiveArgs map[*ir.Name]bool

	doClobber     bool // 是否在此函数中关闭死堆栈插槽。
	noClobberArgs bool // 不要删除函数参数
}

// 将*ssa.Value映射到LivenessIndex。
type Map struct {
	Vals map[ssa.ID]objw.LivenessIndex
	// 在返回时包含变量的活动指针集
	// 调用（仅在使用开放编码延迟时设置）。
	DeferReturn objw.LivenessIndex
}

func (m *Map) reset() {
	if m.Vals == nil {
		m.Vals = make(map[ssa.ID]objw.LivenessIndex)
	} else {
		for k := range m.Vals {
			delete(m.Vals, k)
		}
	}
	m.DeferReturn = objw.LivenessDontCare
}

func (m *Map) set(v *ssa.Value, i objw.LivenessIndex) {
	m.Vals[v.ID] = i
}

func (m Map) Get(v *ssa.Value) objw.LivenessIndex {
	// 如果v不在地图上，那么它是一个“不在乎”而不是一个
	// 不安全点。
	if idx, ok := m.Vals[v.ID]; ok {
		return idx
	}
	return objw.LivenessIndex{StackMapIndex: objw.StackMapDontCare, IsUnsafePoint: false}
}

type progeffectscache struct {
	retuevar    []int32
	tailuevar   []int32
	initialized bool
}

// shouldTrack报告活动性分析
// 应该跟踪变量n。
// 我们不关心没有指针的变量，
// 我们也不关心非局部变量，
// 我们也不关心空结构（由指针检查处理），
// 我们也不关心伪PAUTOHEAP变量。
func shouldTrack(n *ir.Name) bool {
	return (n.Class == ir.PAUTO && n.Esc() != ir.EscHeap || n.Class == ir.PPARAM || n.Class == ir.PPARAMOUT) && n.Type().HasPointers()
}

// getvariables返回我们需要跟踪的堆栈上变量列表
// 以及用于按*节点查找索引的映射。
func getvariables(fn *ir.Func) ([]*ir.Name, map[*ir.Name]int32) {
	var vars []*ir.Name
	for _, n := range fn.Dcl {
		if shouldTrack(n) {
			vars = append(vars, n)
		}
	}
	idx := make(map[*ir.Name]int32, len(vars))
	for i, n := range vars {
		idx[n] = int32(i)
	}
	return vars, idx
}

func (lv *liveness) initcache() {
	if lv.cache.initialized {
		base.Fatalf("liveness cache initialized twice")
		return
	}
	lv.cache.initialized = true

	for i, node := range lv.vars {
		switch node.Class {
		case ir.PPARAM:
			// 带有p.to的返回指令是尾部返回，它会
			// 堆栈指针向上返回（如果它曾经向下），然后跳转
			// 一个全新的功能。这种形式的教学必须阅读
			// 所有参数都是正确的，同样地，它也不能
			// 读出参数-直到新的
			// 函数运行。
			lv.cache.tailuevar = append(lv.cache.tailuevar, int32(i))

		case ir.PPARAMOUT:
			// 所有结果在每个返回点都是实时的。
			// 请注意，这一点是在转义返回值之后
			// 使用它们的PAUTOHEAP引用复制回堆栈。
			lv.cache.retuevar = append(lv.cache.retuevar, int32(i))
		}
	}
}

// liveEffect是一组标志，用于描述指令的
// 活跃度对变量的影响。
// None
// 可能的标志为：
// uevar-由指令使用
// varkill-被指令终止（set）
// kill在使用之后发生（例如，对于更新值的指令）。
type liveEffect int

const (
	uevar liveEffect = 1 << iota
	varkill
)

// valueEffects返回lv.vars中变量的索引和
// v对该变量的活性影响。
// 如果v不影响任何跟踪变量，则返回-1，0。
func (lv *liveness) valueEffects(v *ssa.Value) (int32, liveEffect) {
	n, e := affectedVar(v)
	if e == 0 || n == nil { // 最便宜的支票优先
		return -1, 0
	}
	// AllocFrame已从中删除未使用的变量
	// lv.fn.Func.Dcl，但它们仍可能被引用
	// OpVarFoo伪操作。忽略它们，以防止“丢失跟踪信息”
	// 可变“ICEs”（19632期）。
	switch v.Op {
	case ssa.OpVarDef, ssa.OpVarKill, ssa.OpVarLive, ssa.OpKeepAlive:
		if !n.Used() {
			return -1, 0
		}
	}

	if n.Class == ir.PPARAM && !n.Addrtaken() && n.Type().Width > int64(types.PtrSize) {
		// 只能使用未采用地址的聚合类型参数
		// 部分存活。
		lv.partLiveArgs[n] = true
	}

	var effect liveEffect
	// 显然，读书就是读书。
	// None
	// Addr也是一个read，因为指针的任何后续持有者都必须能够
	// 查看到目前为止写入的所有值（包括初始化）。
	// 这也防止了变量“死而复生”和出现
	// 指向垃圾收集器的陈旧指针。见第28445期。
	if e&(ssa.SymRead|ssa.SymAddr) != 0 {
		effect |= uevar
	}
	if e&ssa.SymWrite != 0 && (!isfat(n.Type()) || v.Op == ssa.OpVarDef) {
		effect |= varkill
	}

	if effect == 0 {
		return -1, 0
	}

	if pos, ok := lv.idx[n]; ok {
		return pos, effect
	}
	return -1, 0
}

// affectedVar返回受v影响的*ir.Name节点
func affectedVar(v *ssa.Value) (*ir.Name, ssa.SymEffect) {
	// 特殊情况。
	switch v.Op {
	case ssa.OpLoadReg:
		n, _ := ssa.AutoVar(v.Args[0])
		return n, ssa.SymRead
	case ssa.OpStoreReg:
		n, _ := ssa.AutoVar(v)
		return n, ssa.SymWrite

	case ssa.OpArgIntReg:
		// 这将强制寄存器的溢出槽在函数输入时处于活动状态。
		// 对于具有指针值寄存器arg X的函数F，以下情况之一适用：
		// 0没有GC（因此未初始化的溢出槽是可以的）
		// 1.GC在F的入口是精确的，但是morestack周围的溢出初始化了X的溢出槽
		// 2.F入口的堆栈增长与GC相同。
		// 3.GC发生在F本身中。这必须来自抢占，因此GC是保守的。
		// A.X在寄存器中——然后X被看到，溢出槽也被保守地扫描。
		// BX溢出——溢出槽被初始化，并被保守地扫描
		// CX不是活动的——溢出槽被保守地扫描，它可能包含来自早期溢出的X。
		// 4.G中的GC，从F传递调用
		// A.X在呼叫站点处于活动状态，因此溢出到其溢出槽（由于随后的LoadReg而处于活动状态）。
		// BX并没有在呼叫现场直播——但它的溢出槽也没有。
		n, _ := ssa.AutoVar(v)
		return n, ssa.SymRead

	case ssa.OpVarLive:
		return v.Aux.(*ir.Name), ssa.SymRead
	case ssa.OpVarDef, ssa.OpVarKill:
		return v.Aux.(*ir.Name), ssa.SymWrite
	case ssa.OpKeepAlive:
		n, _ := ssa.AutoVar(v.Args[0])
		return n, ssa.SymRead
	}

	e := v.Op.SymEffect()
	if e == 0 {
		return nil, 0
	}

	switch a := v.Aux.(type) {
	case nil, *obj.LSym:
		// 好的，但是没有节点
		return nil, e
	case *ir.Name:
		return a, e
	default:
		base.Fatalf("weird aux: %s", v.LongString())
		return nil, e
	}
}

type livenessFuncCache struct {
	be          []blockEffects
	livenessMap Map
}

// 构造一个新的活动结构，用于保存对象的全局状态
// 活性计算。cfg参数是*basicblock的一部分，并且
// vars参数是*节点的一部分。
func newliveness(fn *ir.Func, f *ssa.Func, vars []*ir.Name, idx map[*ir.Name]int32, stkptrsize int64) *liveness {
	lv := &liveness{
		fn:         fn,
		f:          f,
		vars:       vars,
		idx:        idx,
		stkptrsize: stkptrsize,
	}

	// 重要的分配源保存在ssa.Cache中
	// 并重复使用。令人惊讶的是，位向量本身并非如此
	// 这是一个主要的分配来源，但活跃度图是。
	if lc, _ := f.Cache.Liveness.(*livenessFuncCache); lc == nil {
		// 准备缓存，以便活动性稍后可以填充它。
		f.Cache.Liveness = new(livenessFuncCache)
	} else {
		if cap(lc.be) >= f.NumBlocks() {
			lv.be = lc.be[:f.NumBlocks()]
		}
		lv.livenessMap = Map{Vals: lc.livenessMap.Vals, DeferReturn: objw.LivenessDontCare}
		lc.livenessMap.Vals = nil
	}
	if lv.be == nil {
		lv.be = make([]blockEffects, f.NumBlocks())
	}

	nblocks := int32(len(f.Blocks))
	nvars := int32(len(vars))
	bulk := bitvec.NewBulk(nvars, nblocks*7)
	for _, b := range f.Blocks {
		be := lv.blockEffects(b)

		be.uevar = bulk.Next()
		be.varkill = bulk.Next()
		be.livein = bulk.Next()
		be.liveout = bulk.Next()
	}
	lv.livenessMap.reset()

	lv.markUnsafePoints()

	lv.partLiveArgs = make(map[*ir.Name]bool)

	lv.enableClobber()

	return lv
}

func (lv *liveness) blockEffects(b *ssa.Block) *blockEffects {
	return &lv.be[b.ID]
}

// 为参数和局部变量生成活动指针值映射。这个
// 此参数和in参数始终假定为活动参数。瓦尔斯
// 参数是*节点的一部分。
func (lv *liveness) pointerMap(liveout bitvec.BitVec, vars []*ir.Name, args, locals bitvec.BitVec) {
	for i := int32(0); ; i++ {
		i = liveout.Next(i)
		if i < 0 {
			break
		}
		node := vars[i]
		switch node.Class {
		case ir.PPARAM, ir.PPARAMOUT:
			if !node.IsOutputParamInRegisters() {
				if node.FrameOffset() < 0 {
					lv.f.Fatalf("Node %v has frameoffset %d\n", node.Sym().Name, node.FrameOffset())
				}
				typebits.Set(node.Type(), node.FrameOffset(), args)
				break
			}
			fallthrough // 寄存器中的PPARAMOUT像自动分配一样执行内存分配
		case ir.PAUTO:
			typebits.Set(node.Type(), node.FrameOffset()+lv.stkptrsize, locals)
		}
	}
}

// IsUnsafe表示此函数中的所有点都是不安全的
// 不安全点。
func IsUnsafe(f *ssa.Func) bool {
	// 运行时假定只有安全点是函数
	// 开场白（因为过去是这样）。我们可以而且
	// 应该改进这一点，但现在要继续考虑所有要点。
	// 在运行时不安全。obj将添加开场白及其内容
	// 安全点。
	// None
	// go:nosplit函数类似。因为安全点过去
	// 加上堆栈检查，go:nosplit实际上经常出现
	// 表示“此功能中没有安全点”。
	return base.Flag.CompilingRuntime || f.NoSplit
}

// markUnsafePoints查找不安全点并计算lv.unsafePoints。
func (lv *liveness) markUnsafePoints() {
	if IsUnsafe(lv.f) {
		// 不需要复杂的分析。
		lv.allUnsafe = true
		return
	}

	lv.unsafePoints = bitvec.New(int32(lv.f.NumValues()))

	// 标记特定于体系结构的不安全点。
	for _, b := range lv.f.Blocks {
		for _, v := range b.Values {
			if v.Op.UnsafePoint() {
				lv.unsafePoints.Set(int32(v.ID))
			}
		}
	}

	// 标记不安全点。
	for _, wbBlock := range lv.f.WBLoads {
		if wbBlock.Kind == ssa.BlockPlain && len(wbBlock.Values) == 0 {
			// 写屏障块被优化掉了
			// 但我们还没有消除死区。
			// （这可能在-N模式下发生。）
			continue
		}
		// 检查我们是否有预期的菱形。
		if len(wbBlock.Succs) != 2 {
			lv.f.Fatalf("expected branch at write barrier block %v", wbBlock)
		}
		s0, s1 := wbBlock.Succs[0].Block(), wbBlock.Succs[1].Block()
		if s0 == s1 {
			// 写屏障打开和关闭之间没有区别。
			// 因此，不存在不安全的位置。见第26024期。
			continue
		}
		if s0.Kind != ssa.BlockPlain || s1.Kind != ssa.BlockPlain {
			lv.f.Fatalf("expected successors of write barrier block %v to be plain", wbBlock)
		}
		if s0.Succs[0].Block() != s1.Succs[0].Block() {
			lv.f.Fatalf("expected successors of write barrier block %v to converge", wbBlock)
		}

		// 从控制值向后流动以查找
		// 旗帜装载。我们不知道我们在干什么
		// 寻找，但目前所有的拱门都会产生
		// 从标志加载内存的单个操作
		// 地址，所以我们寻找它。
		var load *ssa.Value
		v := wbBlock.Controls[0]
		for {
			if sym, ok := v.Aux.(*obj.LSym); ok && sym == ir.Syms.WriteBarrier {
				load = v
				break
			}
			switch v.Op {
			case ssa.Op386TESTL:
				// 386将Neq32降低至（测试条件），
				if v.Args[0] == v.Args[1] {
					v = v.Args[0]
					continue
				}
			case ssa.Op386MOVLload, ssa.OpARM64MOVWUload, ssa.OpPPC64MOVWZload, ssa.OpWasmI64Load32U:
				// Args[0]是写入的地址
				// 障碍控制。忽略参数[1]，
				// 这是mem操作数。
				// TODO:忽略mem操作数？
				v = v.Args[0]
				continue
			}
			// 常见情况：只是倒流。
			if len(v.Args) != 1 {
				v.Fatalf("write barrier control value has more than one argument: %s", v.LongString())
			}
			v = v.Args[0]
		}

		// 在装载后标记所有内容。
		found := false
		for _, v := range wbBlock.Values {
			found = found || v == load
			if found {
				lv.unsafePoints.Set(int32(v.ID))
			}
		}

		// 将两个后续块标记为不安全。这些来了
		// 直接写入后立即返回到一起
		// 一个后继调用和最后一个写入障碍调用
		// 另一个，所以没有必要更精确。
		for _, succ := range wbBlock.Succs {
			for _, v := range succ.Block().Values {
				lv.unsafePoints.Set(int32(v.ID))
			}
		}
	}

	// 查找uintptr->不安全。指针转换和泛洪
	// 不安全返回通话（这始终是一个安全点）。
	// None
	// 正在查找uintptr->不安全。指针转换有
	// 与寻找不安全的.Pointer->uintpttr相比，没有什么优势
	// 转换：
	// None
	// 1.我们避免不必要地封锁安全点
	// 不安全的.Pointer->uintptr转换永远不会返回到
	// 指针。
	// None
	// 2.我们不必检测reflect.Value.Pointer的调用，
	// reflect.Value.UnsafeAddr和reflect.Value.InterfaceData，
	// 这是隐式的不安全。指针->uintptr转换。
	// 如果有间接证据，我们甚至无法可靠地检测到这一点
	// 调用这些方法之一。
	// None
	// TODO:对于琐碎的不安全指针算法，它将是
	// 很高兴只淹没到不安全的.Pointer->uintpttr
	// 转换，但很难知道Add的哪个参数
	// 或子系统。
	var flooded bitvec.BitVec
	var flood func(b *ssa.Block, vi int)
	flood = func(b *ssa.Block, vi int) {
		if flooded.N == 0 {
			flooded = bitvec.New(int32(lv.f.NumBlocks()))
		}
		if flooded.Get(int32(b.ID)) {
			return
		}
		for i := vi - 1; i >= 0; i-- {
			v := b.Values[i]
			if v.Op.IsCall() {
				// UINTPTR不得包含活动的
				// 指针跨越调用，所以停止
				// 泛滥的。
				return
			}
			lv.unsafePoints.Set(int32(v.ID))
		}
		if vi == len(b.Values) {
			// 我们标记了此块中的所有值，因此没有
			// 需要再次淹没这个街区。
			flooded.Set(int32(b.ID))
		}
		for _, pred := range b.Preds {
			flood(pred.Block(), len(pred.Block().Values))
		}
	}
	for _, b := range lv.f.Blocks {
		for i, v := range b.Values {
			if !(v.Op == ssa.OpConvert && v.Type.IsPtrShaped()) {
				continue
			}
			// 把这不安全的东西倒过来
			// 直到我们接到电话。
			flood(b, i+1)
		}
	}
}

// 对于必须具有堆栈映射的指令，返回true。
// None
// 这并不一定意味着指令是安全点。在里面
// 特别是，调用值可以具有堆栈映射，以防被调用方
// 增加堆栈，但它们本身不是一个安全点。
func (lv *liveness) hasStackMap(v *ssa.Value) bool {
	if !v.Op.IsCall() {
		return false
	}
	// 类型DMEMCLR和类型DMEMMOVE是写屏障和
	// 非常不可抢占。它们是不安全的点和点
	// 因此，不应具有活动性贴图。
	if sym, ok := v.Aux.(*ssa.AuxCall); ok && (sym.Fn == ir.Syms.Typedmemclr || sym.Fn == ir.Syms.Typedmemmove) {
		return false
	}
	return true
}

// 初始化用于求解活动变量的集合。访问所有
// 每个基本块中的说明，用于总结每个基本块中的信息
// 块
func (lv *liveness) prologue() {
	lv.initcache()

	for _, b := range lv.f.Blocks {
		be := lv.blockEffects(b)

		// 向后遍历块指令并更新块
		// 每个prog的效果。
		for j := len(b.Values) - 1; j >= 0; j-- {
			pos, e := lv.valueEffects(b.Values[j])
			if e&varkill != 0 {
				be.varkill.Set(pos)
				be.uevar.Unset(pos)
			}
			if e&uevar != 0 {
				be.uevar.Set(pos)
			}
		}
	}
}

// 求解活跃度数据流方程。
func (lv *liveness) solve() {
	// 这些临时位向量的存在是为了避免连续分配和
	// 在循环中释放。
	nvars := int32(len(lv.vars))
	newlivein := bitvec.New(nvars)
	newliveout := bitvec.New(nvars)

	// 按后序顺序遍历块。这提高了收敛性。
	po := lv.f.Postorder()

	// 以反向循环方式遍历块。作品
	// 队列可能会稍微快一点。按原样，迭代次数为
	// 如此之低，似乎不值得这么复杂。

	for change := true; change; {
		change = false
		for _, b := range po {
			be := lv.blockEffects(b)

			newliveout.Clear()
			switch b.Kind {
			case ssa.BlockRet:
				for _, pos := range lv.cache.retuevar {
					newliveout.Set(pos)
				}
			case ssa.BlockRetJmp:
				for _, pos := range lv.cache.tailuevar {
					newliveout.Set(pos)
				}
			case ssa.BlockExit:
				// 紧急退出-无事可做
			default:
				// 变量在此块的输出上处于活动状态
				// 如果它是活的，则输入到某个后继者。
				// None
				// out[b]=\bigcup{s\in such[b]}in[s]
				newliveout.Copy(lv.blockEffects(b.Succs[0].Block()).livein)
				for _, succ := range b.Succs[1:] {
					newliveout.Or(newliveout, lv.blockEffects(succ.Block()).livein)
				}
			}

			if !be.liveout.Eq(newliveout) {
				change = true
				be.liveout.Copy(newliveout)
			}

			// 变量在此块的输入上处于活动状态
			// 如果它被此块使用，或此块的实时输出，以及
			// 不由此块中的代码设置。
			// None
			// in[b]=uevar[b]\cup（out[b]\set减去varkill[b]）
			newlivein.AndNot(be.liveout, be.varkill)
			be.livein.Or(newlivein, be.uevar)
		}
	}
}

// 访问基本块中的所有指令，并计算活动的位向量
// 每个安全点位置的变量。
func (lv *liveness) epilogue() {
	nvars := int32(len(lv.vars))
	liveout := bitvec.New(nvars)
	livedefer := bitvec.New(nvars) // 总是活的变量

	// 如果存在延迟（可以恢复），则所有输出
	// 参数始终处于活动状态。此外，当地人
	// 是指向堆分配的输出参数的指针
	// 还始终处于活动状态（延迟后返回代码需要这些
	// 将值复制回堆栈的指针）。
	// TODO：如果输出参数是堆分配的，那么我们
	// 不需要保持堆栈副本处于活动状态？
	if lv.fn.HasDefer() {
		for i, n := range lv.vars {
			if n.Class == ir.PPARAMOUT {
				if n.IsOutputParamHeapAddr() {
					// 我只是有点偏执。堆地址是pauto。
					base.Fatalf("variable %v both output param and heap output param", n)
				}
				if n.Heapaddr != nil {
					// 如果此变量移到堆中，则
					// 它的堆栈副本不是活动的。
					continue
				}
				// 注意：归零由walk.go中的zeroResults处理。
				livedefer.Set(int32(i))
			}
			if n.IsOutputParamHeapAddr() {
				// 此变量将在函数的早期被覆盖
				// 序言（来自mallocgc的结果），但我们需要
				// 如果malloc导致堆栈扫描，则将其归零。
				n.SetNeedzero(true)
				livedefer.Set(int32(i))
			}
			if n.OpenDeferSlot() {
				// 打开的编码延迟参数插槽必须处于活动状态
				// 函数中的任何地方，因为恐慌
				// 发生在（几乎）任何地方。因为它是活的
				// 在任何地方，它都必须在进入时归零。
				livedefer.Set(int32(i))
				// 创建时已将其标记为Needzero。
				if !n.Needzero() {
					base.Fatalf("all pointer-containing defer arg slots should have Needzero set")
				}
			}
		}
	}

	// 我们必须首先分析入口块。运行时假定
	// 函数条目映射为索引0。方便，布局
	// 已确保入口块位于第一位。
	if lv.f.Entry != lv.f.Blocks[0] {
		lv.f.Fatalf("entry block must be first")
	}

	{
		// 为函数条目保留一个条目。
		live := bitvec.New(nvars)
		lv.livevars = append(lv.livevars, live)
	}

	for _, b := range lv.f.Blocks {
		be := lv.blockEffects(b)

		// 继续浏览基本的模块说明和
		// 为需要的指令分配活动性映射。
		for _, v := range b.Values {
			if !lv.hasStackMap(v) {
				continue
			}

			live := bitvec.New(nvars)
			lv.livevars = append(lv.livevars, live)
		}

		// 向后走，在每个安全点绘制地图
		index := int32(len(lv.livevars) - 1)

		liveout.Copy(be.liveout)
		for i := len(b.Values) - 1; i >= 0; i-- {
			v := b.Values[i]

			if lv.hasStackMap(v) {
				// 找到一个有趣的指令，记录
				// 相应的活性信息。

				live := &lv.livevars[index]
				live.Or(*live, liveout)
				live.Or(*live, livedefer) // 仅适用于非进入安全点
				index--
			}

			// 更新活跃度信息。
			pos, e := lv.valueEffects(v)
			if e&varkill != 0 {
				liveout.Unset(pos)
			}
			if e&uevar != 0 {
				liveout.Set(pos)
			}
		}

		if b == lv.f.Entry {
			if index != 0 {
				base.Fatalf("bad index for entry point: %v", index)
			}

			// 检查以确保只有输入变量是活动的。
			for i, n := range lv.vars {
				if !liveout.Get(int32(i)) {
					continue
				}
				if n.Class == ir.PPARAM {
					continue // 好啊
				}
				base.FatalfAt(n.Pos(), "bad live variable at entry of %v: %L", lv.fn.Nname, n)
			}

			// 记录活动变量。
			live := &lv.livevars[index]
			live.Or(*live, liveout)
		}

		if lv.doClobber {
			lv.clobber(b)
		}

		// 此块的活动度贴图现在已完成。压缩它们。
		lv.compact(b)
	}

	// 如果我们有一个开放的返回调用，为它制作一个活跃度图。
	if lv.fn.OpenCodedDeferDisallowed() {
		lv.livenessMap.DeferReturn = objw.LivenessDontCare
	} else {
		lv.livenessMap.DeferReturn = objw.LivenessIndex{
			StackMapIndex: lv.stackMapSet.add(livedefer),
			IsUnsafePoint: false,
		}
	}

	// 完成压实。扔掉堆栈映射集。
	lv.stackMaps = lv.stackMapSet.extractUnique()
	lv.stackMapSet = bvecSet{}

	// 有用的健全性检查：在进入函数时，
	// 唯一可能存在的东西是
	// 输入参数。
	for j, n := range lv.vars {
		if n.Class != ir.PPARAM && lv.stackMaps[0].Get(int32(j)) {
			lv.f.Fatalf("%v %L recorded as live on entry", lv.fn.Nname, n)
		}
	}
}

// Compact将lv.livevars中相同的位图合并到集合中
// lv.stackMapSet。
// None
// Compact清除lv.livevars。
// None
// 实际上有两个位图列表，一个是局部变量列表，另一个是局部变量列表
// 函数参数的列表。两个列表都由相同的PCDATA索引
// 索引，因此在
// 合并重复项。过程中，参数位图的更改频率要小得多
// 函数的执行比局部变量的位图要好，因此有可能
// 我们可以为参数和局部变量引入一个单独的PCDATA索引
// 然后分别压缩参数位图集和参数位图集
// 局部变量位图。从2014年4月2日起，对godoc二进制文件执行此操作
// 这实际上是一个净损失：我们节省了大约50k的参数位图，但新的
// PCDATA表的成本约为10万美元。所以现在我们继续使用单个索引
// 两个位图列表。
func (lv *liveness) compact(b *ssa.Block) {
	pos := 0
	if b == lv.f.Entry {
		// 句柄入口堆栈映射。
		lv.stackMapSet.add(lv.livevars[0])
		pos++
	}
	for _, v := range b.Values {
		hasStackMap := lv.hasStackMap(v)
		isUnsafePoint := lv.allUnsafe || v.Op != ssa.OpClobber && lv.unsafePoints.Get(int32(v.ID))
		idx := objw.LivenessIndex{StackMapIndex: objw.StackMapDontCare, IsUnsafePoint: isUnsafePoint}
		if hasStackMap {
			idx.StackMapIndex = lv.stackMapSet.add(lv.livevars[pos])
			pos++
		}
		if hasStackMap || isUnsafePoint {
			lv.livenessMap.set(v, idx)
		}
	}

	// 重置livevars。
	lv.livevars = lv.livevars[:0]
}

func (lv *liveness) enableClobber() {
	// clobberdead实验将代码插入所有内存中的clobber指针插槽
	// 每个同步安全点的死变量（局部变量和参数）。
	if !base.Flag.ClobberDead {
		return
	}
	if lv.fn.Pragma&ir.CgoUnsafeArgs != 0 {
		// C或汇编代码使用精确的框架布局。不要捶打。
		return
	}
	if len(lv.vars) > 10000 || len(lv.f.Blocks) > 10000 {
		// 小心不要做太多的工作。
		// 如果>10000个变量或>10000个块，则为BALL。
		// 否则，巨大的函数会使这个实验产生太多的代码。
		return
	}
	if lv.f.Name == "forkAndExecInChild" {
		// 在某些平台上，forkAndExecInChild调用vfork。
		// 我们在这里添加的代码会破坏子堆栈中的一部分。
		// 当父级恢复时，它使用相同的堆栈帧。但是
		// 子对象具有父对象所需的已删除堆栈变量。繁荣
		// 特别是，sys参数会被截断。
		return
	}
	if lv.f.Name == "wbBufFlush" ||
		((lv.f.Name == "callReflect" || lv.f.Name == "callMethod") && lv.fn.ABIWrapper()) {
		// runtime.wbBufFlush不能修改其参数。见评论
		// 在运行时/mwbbuf.go:wbbuflush。
		// None
		// reflect.callReflect和reflect.callMethod是从特殊的
		// 函数makeFuncStub和methodValueCall。运行时期望
		// 它可以在makeFuncStub中的0（SP）处找到第一个参数（ctxt）
		// 和methodValueCall的框架（请参阅runtime/traceback.go:getArgInfo）。
		// 通常，callReflect和callMethod不会修改
		// 争论，并保持它的活力。但是编译器生成了ABI包装器
		// 不要那样做。特殊情况下，包装器不会破坏其参数。
		lv.noClobberArgs = true
	}
	if h := os.Getenv("GOCLOBBERDEADHASH"); h != "" {
		// Clobber仅用于函数名的哈希值与模式匹配的函数。
		// 用于对错误编译的函数进行二进制搜索。
		hstr := ""
		for _, b := range sha1.Sum([]byte(lv.f.Name)) {
			hstr += fmt.Sprintf("%08b", b)
		}
		if !strings.HasSuffix(hstr, h) {
			return
		}
		fmt.Printf("\t\t\tCLOBBERDEAD %s\n", lv.f.Name)
	}
	lv.doClobber = true
}

// 在所有死变量（局部变量和参数）中插入代码以关闭指针槽
// 在b中的每个同步安全点。
func (lv *liveness) clobber(b *ssa.Block) {
	// 将块的值复制到临时文件。
	oldSched := append([]*ssa.Value{}, b.Values...)
	b.Values = b.Values[:0]
	idx := 0

	// 在入口的所有死变量中都有Clobber指针槽。
	if b == lv.f.Entry {
		for len(oldSched) > 0 && len(oldSched[0].Args) == 0 {
			// 跳过无arg操作。我们至少需要跳过
			// 降低ClosurePtr op，因为它
			// 我真的想成为第一。这也将
			// 跳过像InitMem和SP这样的操作，它们都可以。
			b.Values = append(b.Values, oldSched[0])
			oldSched = oldSched[1:]
		}
		clobber(lv, b, lv.livevars[0])
		idx++
	}

	// 将值复制到计划中，在安全点周围添加碰撞。
	for _, v := range oldSched {
		if !lv.hasStackMap(v) {
			b.Values = append(b.Values, v)
			continue
		}
		clobber(lv, b, lv.livevars[idx])
		b.Values = append(b.Values, v)
		idx++
	}
}

// clobber生成代码来clobber所有死变量中的指针槽
// （未在live中标记的）。结尾添加了重击指令
// b.价值观。
func clobber(lv *liveness, b *ssa.Block, live bitvec.BitVec) {
	for i, n := range lv.vars {
		if !live.Get(int32(i)) && !n.Addrtaken() && !n.OpenDeferSlot() && !n.IsOutputParamHeapAddr() {
			// 不要删除堆栈对象（地址已获取）。他们是
			// 动态跟踪。
			// 另外，也不要破坏为延迟而使用的插槽（请参阅
			// 结束语中的代码设置livedefer）。
			if lv.noClobberArgs && n.Class == ir.PPARAM {
				continue
			}
			clobberVar(b, n)
		}
	}
}

// clobberVar生成代码以丢弃v中的指针。
// 将删除指令添加到b值的末尾。
func clobberVar(b *ssa.Block, v *ir.Name) {
	clobberWalk(b, v, 0, v.Type())
}

// b=我们附加指令的块
// v=变量
// offset=变量（的子部分）到clobber的偏移量（字节）
// t=v的子部分类型。
func clobberWalk(b *ssa.Block, v *ir.Name, offset int64, t *types.Type) {
	if !t.HasPointers() {
		return
	}
	switch t.Kind() {
	case types.TPTR,
		types.TUNSAFEPTR,
		types.TFUNC,
		types.TCHAN,
		types.TMAP:
		clobberPtr(b, v, offset)

	case types.TSTRING:
		// 结构{byte*str；int len；}
		clobberPtr(b, v, offset)

	case types.TINTER:
		// 结构{Itab*选项卡；void*数据；}
		// 或者，当isnilinter（t）=真时：
		// 结构{Type*Type；void*data；}
		clobberPtr(b, v, offset)
		clobberPtr(b, v, offset+int64(types.PtrSize))

	case types.TSLICE:
		// 结构{byte*数组；int len；int cap；}
		clobberPtr(b, v, offset)

	case types.TARRAY:
		for i := int64(0); i < t.NumElem(); i++ {
			clobberWalk(b, v, offset+i*t.Elem().Size(), t.Elem())
		}

	case types.TSTRUCT:
		for _, t1 := range t.Fields().Slice() {
			clobberWalk(b, v, offset+t1.Offset, t1.Type)
		}

	default:
		base.Fatalf("clobberWalk: unexpected type, %v", t)
	}
}

// clobberPtr在v中的偏移量处生成指针的clobber。
// clobber指令添加在b的末尾。
func clobberPtr(b *ssa.Block, v *ir.Name, offset int64) {
	b.NewValue0IA(src.NoXPos, ssa.OpClobber, types.TypeVoid, offset, v)
}

func (lv *liveness) showlive(v *ssa.Value, live bitvec.BitVec) {
	if base.Flag.Live == 0 || ir.FuncName(lv.fn) == "init" || strings.HasPrefix(ir.FuncName(lv.fn), ".") {
		return
	}
	if !(v == nil || v.Op.IsCall()) {
		// 历史上，我们只在
		// 电话。继续这样做。
		return
	}
	if live.IsEmpty() {
		return
	}

	pos := lv.fn.Nname.Pos()
	if v != nil {
		pos = v.Pos
	}

	s := "live at "
	if v == nil {
		s += fmt.Sprintf("entry to %s:", ir.FuncName(lv.fn))
	} else if sym, ok := v.Aux.(*ssa.AuxCall); ok && sym.Fn != nil {
		fn := sym.Fn.Name
		if pos := strings.Index(fn, "."); pos >= 0 {
			fn = fn[pos+1:]
		}
		s += fmt.Sprintf("call to %s:", fn)
	} else {
		s += "indirect call:"
	}

	for j, n := range lv.vars {
		if live.Get(int32(j)) {
			s += fmt.Sprintf(" %v", n)
		}
	}

	base.WarnfAt(pos, s)
}

func (lv *liveness) printbvec(printed bool, name string, live bitvec.BitVec) bool {
	if live.IsEmpty() {
		return printed
	}

	if !printed {
		fmt.Printf("\t")
	} else {
		fmt.Printf(" ")
	}
	fmt.Printf("%s=", name)

	comma := ""
	for i, n := range lv.vars {
		if !live.Get(int32(i)) {
			continue
		}
		fmt.Printf("%s%s", comma, n.Sym().Name)
		comma = ","
	}
	return true
}

// printeffect与printbvec类似，但适用于valueEffects。
func (lv *liveness) printeffect(printed bool, name string, pos int32, x bool) bool {
	if !x {
		return printed
	}
	if !printed {
		fmt.Printf("\t")
	} else {
		fmt.Printf(" ")
	}
	fmt.Printf("%s=", name)
	if x {
		fmt.Printf("%s", lv.vars[pos].Sym().Name)
	}

	return true
}

// 打印计算的活动度信息和输入，以进行调试。
// 此格式合成多个过程中使用的信息
// 变成一个单独的演示。
func (lv *liveness) printDebug() {
	fmt.Printf("liveness: %s\n", ir.FuncName(lv.fn))

	for i, b := range lv.f.Blocks {
		if i > 0 {
			fmt.Printf("\n")
		}

		// bb#0 pred=1,2 succ=3,4
		fmt.Printf("bb#%d pred=", b.ID)
		for j, pred := range b.Preds {
			if j > 0 {
				fmt.Printf(",")
			}
			fmt.Printf("%d", pred.Block().ID)
		}
		fmt.Printf(" succ=")
		for j, succ := range b.Succs {
			if j > 0 {
				fmt.Printf(",")
			}
			fmt.Printf("%d", succ.Block().ID)
		}
		fmt.Printf("\n")

		be := lv.blockEffects(b)

		// 初始设置
		printed := false
		printed = lv.printbvec(printed, "uevar", be.uevar)
		printed = lv.printbvec(printed, "livein", be.livein)
		if printed {
			fmt.Printf("\n")
		}

		// 程序列表，列出单个效果

		if b == lv.f.Entry {
			live := lv.stackMaps[0]
			fmt.Printf("(%s) function entry\n", base.FmtPos(lv.fn.Nname.Pos()))
			fmt.Printf("\tlive=")
			printed = false
			for j, n := range lv.vars {
				if !live.Get(int32(j)) {
					continue
				}
				if printed {
					fmt.Printf(",")
				}
				fmt.Printf("%v", n)
				printed = true
			}
			fmt.Printf("\n")
		}

		for _, v := range b.Values {
			fmt.Printf("(%s) %v\n", base.FmtPos(v.Pos), v.LongString())

			pcdata := lv.livenessMap.Get(v)

			pos, effect := lv.valueEffects(v)
			printed = false
			printed = lv.printeffect(printed, "uevar", pos, effect&uevar != 0)
			printed = lv.printeffect(printed, "varkill", pos, effect&varkill != 0)
			if printed {
				fmt.Printf("\n")
			}

			if pcdata.StackMapValid() {
				fmt.Printf("\tlive=")
				printed = false
				if pcdata.StackMapValid() {
					live := lv.stackMaps[pcdata.StackMapIndex]
					for j, n := range lv.vars {
						if !live.Get(int32(j)) {
							continue
						}
						if printed {
							fmt.Printf(",")
						}
						fmt.Printf("%v", n)
						printed = true
					}
				}
				fmt.Printf("\n")
			}

			if pcdata.IsUnsafePoint {
				fmt.Printf("\tunsafe-point\n")
			}
		}

		// bb位集
		fmt.Printf("end\n")
		printed = false
		printed = lv.printbvec(printed, "varkill", be.varkill)
		printed = lv.printbvec(printed, "liveout", be.liveout)
		if printed {
			fmt.Printf("\n")
		}
	}

	fmt.Printf("\n")
}

// 将位图片段作为uint32值序列转储到符号。这个
// 第一个字是位图的总数。第二个词是
// 位图的长度。假定所有位图的长度相等。这个
// 剩余字节是原始位图。
func (lv *liveness) emit() (argsSym, liveSym *obj.LSym) {
	// 将参数位图的大小设置为刚好足以容纳最大指针。
	// 首先，找到我们关心的最大Xoffset节点。
	// （没有指针的节点不在lv.vars中；请参见ShouldTrack。）
	var maxArgNode *ir.Name
	for _, n := range lv.vars {
		switch n.Class {
		case ir.PPARAM, ir.PPARAMOUT:
			if !n.IsOutputParamInRegisters() {
				if maxArgNode == nil || n.FrameOffset() > maxArgNode.FrameOffset() {
					maxArgNode = n
				}
			}
		}
	}
	// 接下来，查找最大节点中最大指针的偏移量。
	var maxArgs int64
	if maxArgNode != nil {
		maxArgs = maxArgNode.FrameOffset() + types.PtrDataSize(maxArgNode.Type())
	}

	// 将本地位图的大小设置为stkptrsize大小。
	// 我们不能将它们缩小到只容纳最大的指针，
	// 因为它们的大小是用来计算开始的
	// 局部变量的框架。
	// https中的进一步讨论：
	// ToDo：考虑调整前导零点。
	// 这需要移动所有位图。
	maxLocals := lv.stkptrsize

	// 用于编码位图的临时符号。
	var argsSymTmp, liveSymTmp obj.LSym

	args := bitvec.New(int32(maxArgs / int64(types.PtrSize)))
	aoff := objw.Uint32(&argsSymTmp, 0, uint32(len(lv.stackMaps))) // 位图的数量
	aoff = objw.Uint32(&argsSymTmp, aoff, uint32(args.N))          // 每个位图中的位数

	locals := bitvec.New(int32(maxLocals / int64(types.PtrSize)))
	loff := objw.Uint32(&liveSymTmp, 0, uint32(len(lv.stackMaps))) // 位图的数量
	loff = objw.Uint32(&liveSymTmp, loff, uint32(locals.N))        // 每个位图中的位数

	for _, live := range lv.stackMaps {
		args.Clear()
		locals.Clear()

		lv.pointerMap(live, lv.vars, args, locals)

		aoff = objw.BitVec(&argsSymTmp, aoff, args)
		loff = objw.BitVec(&liveSymTmp, loff, locals)
	}

	// 给出这些LSyms内容的可寻址名称，
	// 这样就可以消除重复。
	// 这大大节省了二进制大小。
	// None
	// 这些符号将通过addGCLocals添加到Ctxt.Data中
	// 并行编译完成后。
	makeSym := func(tmpSym *obj.LSym) *obj.LSym {
		return base.Ctxt.LookupInit(fmt.Sprintf("gclocals·%x", md5.Sum(tmpSym.P)), func(lsym *obj.LSym) {
			lsym.P = tmpSym.P
			lsym.Set(obj.AttrContentAddressable, true)
		})
	}
	return makeSym(&argsSymTmp), makeSym(&liveSymTmp)
}

// 用于计算分析的入口指针。解的计算
// 函数中的指针变量，并发出运行时数据
// 由垃圾收集器读取的结构。
// 返回从GC安全点到相应堆栈映射索引的映射，
// 以及一个包含可能部分活动的所有输入参数的映射。
func Compute(curfn *ir.Func, f *ssa.Func, stkptrsize int64, pp *objw.Progs) (Map, map[*ir.Name]bool) {
	// 构造全局活性状态。
	vars, idx := getvariables(curfn)
	lv := newliveness(curfn, f, vars, idx, stkptrsize)

	// 运行数据流框架。
	lv.prologue()
	lv.solve()
	lv.epilogue()
	if base.Flag.Live > 0 {
		lv.showlive(nil, lv.stackMaps[0])
		for _, b := range f.Blocks {
			for _, val := range b.Values {
				if idx := lv.livenessMap.Get(val); idx.StackMapValid() {
					lv.showlive(val, lv.stackMaps[idx.StackMapIndex])
				}
			}
		}
	}
	if base.Flag.Live >= 2 {
		lv.printDebug()
	}

	// 更新函数缓存。
	{
		cache := f.Cache.Liveness.(*livenessFuncCache)
		if cap(lv.be) < 2000 { // 来自ssa.Cache切片的阈值。
			for i := range lv.be {
				lv.be[i] = blockEffects{}
			}
			cache.be = lv.be
		}
		if len(lv.livenessMap.Vals) < 2000 {
			cache.livenessMap = lv.livenessMap
		}
	}

	// 发出活动指针映射数据结构
	ls := curfn.LSym
	fninfo := ls.Func()
	fninfo.GCArgs, fninfo.GCLocals = lv.emit()

	p := pp.Prog(obj.AFUNCDATA)
	p.From.SetConst(objabi.FUNCDATA_ArgsPointerMaps)
	p.To.Type = obj.TYPE_MEM
	p.To.Name = obj.NAME_EXTERN
	p.To.Sym = fninfo.GCArgs

	p = pp.Prog(obj.AFUNCDATA)
	p.From.SetConst(objabi.FUNCDATA_LocalsPointerMaps)
	p.To.Type = obj.TYPE_MEM
	p.To.Name = obj.NAME_EXTERN
	p.To.Sym = fninfo.GCLocals

	if x := lv.emitStackObjects(); x != nil {
		p := pp.Prog(obj.AFUNCDATA)
		p.From.SetConst(objabi.FUNCDATA_StackObjects)
		p.To.Type = obj.TYPE_MEM
		p.To.Name = obj.NAME_EXTERN
		p.To.Sym = x
	}

	return lv.livenessMap, lv.partLiveArgs
}

func (lv *liveness) emitStackObjects() *obj.LSym {
	var vars []*ir.Name
	for _, n := range lv.fn.Dcl {
		if shouldTrack(n) && n.Addrtaken() && n.Esc() != ir.EscHeap {
			vars = append(vars, n)
		}
	}
	if len(vars) == 0 {
		return nil
	}

	// 将变量从最低地址排序到最高地址。
	sort.Slice(vars, func(i, j int) bool { return vars[i].FrameOffset() < vars[j].FrameOffset() })

	// 填充堆栈对象数据。
	// 格式必须与运行时/堆栈匹配。转到：stackObjectRecord。
	x := base.Ctxt.Lookup(lv.fn.LSym.Name + ".stkobj")
	lv.fn.LSym.Func().StackObjects = x
	off := 0
	off = objw.Uintptr(x, off, uint64(len(vars)))
	for _, v := range vars {
		// 注意：参数和返回值具有非负Xoffset，
		// 在这种情况下，偏移量相对于argp。
		// 局部变量具有负Xoffset，在这种情况下，偏移量相对于varp。
		// 我们已经限制了帧大小，所以偏移量和对象大小
		// 不应该太大。
		frameOffset := v.FrameOffset()
		if frameOffset != int64(int32(frameOffset)) {
			base.Fatalf("frame offset too big: %v %d", v, frameOffset)
		}
		off = objw.Uint32(x, off, uint32(frameOffset))

		t := v.Type()
		sz := t.Width
		if sz != int64(int32(sz)) {
			base.Fatalf("stack object too big: %v of type %v, size %d", v, t, sz)
		}
		lsym, useGCProg, ptrdata := reflectdata.GCSym(t)
		if useGCProg {
			ptrdata = -ptrdata
		}
		off = objw.Uint32(x, off, uint32(sz))
		off = objw.Uint32(x, off, uint32(ptrdata))
		off = objw.SymPtr(x, off, lsym, 0)
	}

	if base.Flag.Live != 0 {
		for _, v := range vars {
			base.WarnfAt(v.Pos(), "stack object %v %v", v, v.Type())
		}
	}

	return x
}

// isfat报告t类型的变量是否需要多次赋值才能初始化。
// 例如：
// None
// 类型T结构{x，y int}
// x:=T{x:0，y:1}
// None
// 那么我们需要：
// None
// 变量t
// t、 x=0
// t、 y=1
// None
// 完全初始化t。
func isfat(t *types.Type) bool {
	if t != nil {
		switch t.Kind() {
		case types.TSLICE, types.TSTRING,
			types.TINTER: // 也许以后再搬
			return true
		case types.TARRAY:
			// 1个元素的数组，检查元素是否为fat
			if t.NumElem() == 1 {
				return isfat(t.Elem())
			}
			return true
		case types.TSTRUCT:
			// 带1个字段的结构，检查字段是否为fat
			if t.NumFields() == 1 {
				return isfat(t.Field(0).Type)
			}
			return true
		}
	}

	return false
}

// WriteFuncMap为无正文函数fn写入指针位图
// 输入和输出为symbol<fn>.args\u stackmap的值。
// 如果fn有输出，则写入两个位图，否则仅写入一个位图。
func WriteFuncMap(fn *ir.Func, abiInfo *abi.ABIParamResultInfo) {
	if ir.FuncName(fn) == "_" || fn.Sym().Linkname != "" {
		return
	}
	nptr := int(abiInfo.ArgWidth() / int64(types.PtrSize))
	bv := bitvec.New(int32(nptr) * 2)

	for _, p := range abiInfo.InParams() {
		typebits.Set(p.Type, p.FrameOffset(abiInfo), bv)
	}

	nbitmap := 1
	if fn.Type().NumResults() > 0 {
		nbitmap = 2
	}
	lsym := base.Ctxt.Lookup(fn.LSym.Name + ".args_stackmap")
	off := objw.Uint32(lsym, 0, uint32(nbitmap))
	off = objw.Uint32(lsym, off, uint32(bv.N))
	off = objw.BitVec(lsym, off, bv)

	if fn.Type().NumResults() > 0 {
		for _, p := range abiInfo.OutParams() {
			if len(p.Registers) == 0 {
				typebits.Set(p.Type, p.FrameOffset(abiInfo), bv)
			}
		}
		off = objw.BitVec(lsym, off, bv)
	}

	objw.Global(lsym, int32(off), obj.RODATA|obj.LOCAL)
}
